#include "preHeader.h"
#include "ItemStorage.h"
#include "Map/MapInstance.h"
#include "Spell/SpellMgr.h"
#include "MapServer.h"

#define ICF_NO_EFFECT_FLAGS \
	(ICF_PREVENT_SYNC_CLIENT | ICF_PREVENT_SYNC_VALUE)

ItemStorage::ItemStorage(Player* pOwner)
: m_pOwner(pOwner)
, m_bagCapacity(ItemBagSlotDefCount)
, m_bankCapacity(ItemBankSlotDefCount)
{
	std::fill_n(m_equipItems, ARRAY_SIZE(m_equipItems), nullptr);
	std::fill_n(m_bagItems, ARRAY_SIZE(m_bagItems), nullptr);
	std::fill_n(m_bankItems, ARRAY_SIZE(m_bankItems), nullptr);
}

ItemStorage::~ItemStorage()
{
	for (auto pItem : m_equipItems) { delete pItem; }
	for (auto pItem : m_bagItems) { delete pItem; }
	for (auto pItem : m_bankItems) { delete pItem; }
}

void ItemStorage::SetBagCapacity(uint32 capacity)
{
	m_bagCapacity = std::min(
		std::max(capacity, ItemBagSlotDefCount), ItemBagSlotCount);
}

void ItemStorage::SetBankCapacity(uint32 capacity)
{
	m_bankCapacity = std::min(
		std::max(capacity, ItemBankSlotDefCount), ItemBankSlotCount);
}

std::pair<Item* const*, uint32> ItemStorage::GetItemCluster(
	ItemSlotType type, uint32 slot) const
{
	switch (type) {
	case ItemSlotEquipType:
		if (slot == ItemSlotInvalid || slot < ItemEquipSlotCount) {
			return { m_equipItems, ItemEquipSlotCount };
		}
		break;
	case ItemSlotBagType:
		if (slot == ItemSlotInvalid || slot < m_bagCapacity) {
			return { m_bagItems, m_bagCapacity };
		}
		break;
	case ItemSlotBankType:
		if (slot == ItemSlotInvalid || slot < m_bankCapacity) {
			return { m_bankItems, m_bankCapacity };
		}
		break;
	}
	return { nullptr, 0 };
}

std::pair<Item**, uint32> ItemStorage::GetItemClusterMutable(
	ItemSlotType type, uint32 slot)
{
	auto rst = GetItemCluster(type);
	return { const_cast<Item**>(rst.first), rst.second };
}

Item* ItemStorage::GetItem(ItemSlotType type, uint32 slot) const
{
	auto pItems = GetItemCluster(type, slot).first;
	return pItems != NULL ? pItems[slot] : NULL;
}

void ItemStorage::ForeachItem(
	ItemSlotType type, const std::function<void(uint32 i, Item*)>& func) const
{
	Item* const * pItemCluster = NULL;
	uint32 slotCapacity = 0;
	std::tie(pItemCluster, slotCapacity) = GetItemCluster(type);
	for (uint32 i = 0; i < slotCapacity; ++i) {
		auto pItem = pItemCluster[i];
		if (pItem != NULL) {
			func(i, pItem);
		}
	}
}

GErrorCode ItemStorage::DestroyItem(uint32 slot, uint32 num)
{
	if (slot >= m_bagCapacity) {
		return InvalidRequest;
	}
	auto pItem = m_bagItems[slot];
	if (pItem == NULL) {
		return InvalidRequest;
	}
	if (!pItem->GetItemProto()->itemFlags.canDestroy) {
		return InvalidRequest;
	}
	if (pItem->GetItemCount() < num) {
		return InvalidRequest;
	}

	RemoveCountSlotItem(ItemSlotBagType,
		slot, num != 0 ? num : pItem->GetItemCount(), IFT_ITEM_DESTROY, {});

	return CommonSuccess;
}

GErrorCode ItemStorage::UseItem(uint32 slot, uint32 num, const std::string_view& udata)
{
	if (slot >= m_bagCapacity || num == 0) {
		return InvalidRequest;
	}
	auto pItem = m_bagItems[slot];
	if (pItem == NULL) {
		return InvalidRequest;
	}
	if (!pItem->GetItemProto()->itemFlags.canUse) {
		return InvalidRequest;
	}
	if (pItem->GetItemCount() < num) {
		return InvalidRequest;
	}

	GErrorCode errCode = CanItemUse4Loot(pItem, num, udata);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanItemUse4Spell(pItem, num, udata);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanItemUse4Script(pItem, num, udata);
	if (errCode != CommonSuccess) {
		return errCode;
	}

	auto actionUniqueKey = m_pOwner->NewActionUniqueKey();
	int useRsts[] = {
		UseItem4Loot(actionUniqueKey, pItem, num, udata),
		UseItem4Spell(actionUniqueKey, pItem, num, udata),
		UseItem4Script(actionUniqueKey, pItem, num, udata),
	};
	int rst = *std::max_element(std::begin(useRsts), std::end(useRsts));
	if (rst == 0) {
		if (pItem->GetItemProto()->itemFlags.isDestroyAfterUse) {
			rst = 1;
		}
	}
	if (rst > 0) {
		m_pOwner->GetQuestStorage()->
			OnUseItem(pItem->GetItemTypeID(), rst, actionUniqueKey);
		RemoveCountSlotItem(ItemSlotBagType, slot, rst, IFT_ITEM_USE, {});
	}

	return CommonSuccess;
}

GErrorCode ItemStorage::CanItemUse4Loot(
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemLootId == 0) {
		return CommonSuccess;
	}
	return m_pOwner->CanLootPrizes2ItemStorage(
		pItemProto->itemLootId, false);
}

int ItemStorage::UseItem4Loot(uint32 actionUniqueKey,
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemLootId == 0) {
		return 0;
	}
	m_pOwner->LootPrizes2ItemStorage(pItemProto->itemLootId,
		IFT_ITEM_USE, {pItemProto->itemTypeID},
		CFT_ITEM_USE, {pItemProto->itemTypeID}, true);
	return 0;
}

GErrorCode ItemStorage::CanItemUse4Spell(
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemSpellId == 0) {
		return CommonSuccess;
	}
	return m_pOwner->CanCastWithoutLearnSpell(pItemProto->itemSpellId,
		pItemProto->itemSpellLevel, 0, vector3f_INVALID, udata);
}

int ItemStorage::UseItem4Spell(uint32 actionUniqueKey,
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemSpellId == 0) {
		return 0;
	}
	m_pOwner->CastWithoutLearnSpell(pItemProto->itemSpellId,
		pItemProto->itemSpellLevel, 0, vector3f_INVALID, udata);
	return 0;
}

GErrorCode ItemStorage::CanItemUse4Script(
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemScriptId == 0) {
		return CommonSuccess;
	}
	auto& scriptFile = GetScriptFileById(pItemProto->itemScriptId);
	if (scriptFile.empty()) {
		return CommonInternalError;
	}
	auto L = pItem->GetOwner()->GetMapInstance()->L;
	if (!sLuaMgr.DoFile(L, scriptFile)) {
		return CommonInternalError;
	}
	return LuaFunc(L, "CanItemUse").Call<GErrorCode>(
		pItem, num, std::string_view(pItemProto->itemScriptArgs), udata);
}

int ItemStorage::UseItem4Script(uint32 actionUniqueKey,
	Item* pItem, uint32 num, const std::string_view& udata) const
{
	auto pItemProto = pItem->GetItemProto();
	if (pItemProto->itemScriptId == 0) {
		return 0;
	}
	auto L = pItem->GetOwner()->GetMapInstance()->L;
	return LuaFunc(L, "UseItem").Call<int>(
		pItem, num, std::string_view(pItemProto->itemScriptArgs), udata,
		actionUniqueKey);
}

void ItemStorage::RemoveCountSlotItem(
	ItemSlotType type, uint32 slot,
	uint32 num, ITEM_FLOW_TYPE flowType, params<uint32> flowParams)
{
	auto pItem = GetItem(type, slot);
	if (pItem == NULL) {
		return;
	}

	if (pItem->GetItemCount() > num) {
		pItem->SubItemCount(num, flowType, flowParams);
	} else {
		RemoveSlotItem(type, slot, flowType, flowParams);
	}
}

void ItemStorage::RemoveSlotItem(ItemSlotType type,
	uint32 slot, ITEM_FLOW_TYPE flowType, params<uint32> flowParams)
{
	auto pItems = GetItemClusterMutable(type, slot).first;
	if (pItems == NULL) {
		return;
	}

	auto& pItem = pItems[slot];
	auto pQuestStorage = m_pOwner->GetQuestStorage();
	pQuestStorage->SetItemCountDirty(pItem->GetItemTypeID());
	pQuestStorage->OnHaveItem(
		pItem->GetItemTypeID(), 0, flowType, flowParams);

	NetPacket pack(SMSG_REMOVE_ITEM);
	pack << type << slot << pItem->GetItemGuid();
	m_pOwner->SendPacket(pack);

	SAFE_DELETE(pItem);
}

bool ItemStorage::IsItemEnough(
	uint32 itemTypeID, uint32 itemCount, uint32 flags) const
{
	return IsItemsEnough(&itemTypeID, 1, flags, itemCount);
}

bool ItemStorage::IsItemsEnough(const uint32 itemTypeIDs[],
	size_t itemSize, uint32 itemCount, uint32 flags) const
{
	return GetItemsAmount(itemTypeIDs, itemSize, flags, itemCount) >= itemCount;
}

uint32 ItemStorage::GetItemAmount(
	uint32 itemTypeID, uint32 flags, uint32 hints) const
{
	return GetItemsAmount(&itemTypeID, 1, flags, hints);
}

// flags = ISF_INCLUDE_BAG|
//         ISF_INCLUDE_BINDING|ISF_INCLUDE_NONBINDING
uint32 ItemStorage::GetItemsAmount(const uint32 itemTypeIDs[], size_t itemSize,
	uint32 flags, uint32 hints) const
{
	uint32 itemCnt = 0;
	auto AccumulateClusterItemCount =
		[=, &itemCnt](Item* const* pItems, uint32 slotCapacity) {
		for (u32 iSlot = 0; iSlot < slotCapacity && itemCnt < hints; ++iSlot) {
			auto pItem = pItems[iSlot];
			if (pItem == NULL) {
				continue;
			}
			if ((flags & ISF_EXCLUDE_BINDING) && pItem->IsItemBinding()) {
				continue;
			}
			if ((flags & ISF_EXCLUDE_NONBINDING) && !pItem->IsItemBinding()) {
				continue;
			}
			if (!IS_INPTR_CONTAIN_VALUE(itemTypeIDs, itemSize, pItem->GetItemTypeID())) {
				continue;
			}
			itemCnt += pItem->GetItemCount();
		}
	};

	if ((flags & ISF_EXCLUDE_BAG) == 0) {
		AccumulateClusterItemCount(m_bagItems, m_bagCapacity);
	}
	if ((flags & ISF_INCLUDE_BANK) != 0) {
		AccumulateClusterItemCount(m_bankItems, m_bankCapacity);
	}
	if ((flags & ISF_INCLUDE_EQUIP) != 0) {
		AccumulateClusterItemCount(m_equipItems, ItemEquipSlotCount);
	}

	return itemCnt;
}

GErrorCode ItemStorage::RemoveItemAmount(
	uint32 itemTypeID, uint32 itemCount,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, uint32 flags)
{
	return RemoveItemsAmount(&itemTypeID, 1, itemCount, flowType, flowParams, flags);
}

// flags = ISF_INCLUDE_BAG|ISF_INCLUDE_BANK|
//         ISF_INCLUDE_BINDING|ISF_INCLUDE_NONBINDING
// prior = ISF_INCLUDE_BINDING
GErrorCode ItemStorage::RemoveItemsAmount(
	const uint32 itemTypeIDs[], size_t itemSize, uint32 itemCount,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, uint32 flags)
{
	auto RemoveClusterItemAmount = [=, &itemCount](Item** pItems,
		ItemSlotType slotType, uint32 slotCapacity, bool isBinding) {
		for (u32 iSlot = 0; iSlot < slotCapacity && itemCount > 0; ++iSlot) {
			auto pItem = pItems[iSlot];
			if (pItem == NULL) {
				continue;
			}
			if (!isBinding && pItem->IsItemBinding()) {
				continue;
			}
			if (isBinding && !pItem->IsItemBinding()) {
				continue;
			}
			if (!IS_INPTR_CONTAIN_VALUE(itemTypeIDs, itemSize, pItem->GetItemTypeID())) {
				continue;
			}
			uint32 theItemCount = pItem->GetItemCount();
			RemoveCountSlotItem(slotType, iSlot, itemCount, flowType, flowParams);
			SubLeastZeroX(itemCount, theItemCount);
		}
	};

	auto RemoveXXXBindingItemAmount =
		[=, &RemoveClusterItemAmount](bool isBinding) {
		if ((flags & ISF_EXCLUDE_BAG) == 0) {
			RemoveClusterItemAmount(
				m_bagItems, ItemSlotBagType, m_bagCapacity, isBinding);
		}
		if ((flags & ISF_EXCLUDE_BANK) == 0) {
			RemoveClusterItemAmount(
				m_bankItems, ItemSlotBankType, m_bankCapacity, isBinding);
		}
		if ((flags & ISF_INCLUDE_EQUIP) != 0) {
			RemoveClusterItemAmount(
				m_equipItems, ItemSlotEquipType, ItemEquipSlotCount, isBinding);
		}
	};

	if ((flags & ISF_EXCLUDE_BINDING) == 0) {
		RemoveXXXBindingItemAmount(true);
	}
	if ((flags & ISF_EXCLUDE_NONBINDING) == 0) {
		RemoveXXXBindingItemAmount(false);
	}

	if (itemCount > 0) {
		return ErrItemCountNotEnough;
	} else {
		return CommonSuccess;
	}
}

GErrorCode ItemStorage::RemoveItemAmountBySlot(
	ItemSlotType type, uint32 itemSlot,
	uint32 itemCount, ITEM_FLOW_TYPE flowType, params<uint32> flowParams)
{
	Item* pItem = GetItem(type, itemSlot);
	if (pItem == NULL) {
		return ErrItemNotExist;
	}

	uint32 theItemCount = pItem->GetItemCount();
	RemoveCountSlotItem(type, itemSlot, itemCount, flowType, flowParams);
	SubLeastZeroX(itemCount, theItemCount);

	if (itemCount > 0) {
		return ErrItemCountNotEnough;
	} else {
		return CommonSuccess;
	}
}

void ItemStorage::CreateAddItemMail(
	const ItemPrototype* pItemProto, const inst_item_prop& itemProp,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, bool isSendPopMsg,
	Item** pNewItemPtr)
{
	CreateAddItemsMail(pItemProto != NULL ? &pItemProto : NULL, &itemProp,
		1, flowType, flowParams, isSendPopMsg, pNewItemPtr);
}

void ItemStorage::CreateAddItemsMail(
	const ItemPrototype* const* pItemProtos,
	const inst_item_prop itemProps[], size_t itemSize,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, bool isSendPopMsg,
	Item** pNewItemPtr)
{
	std::vector<uint32> itemSurplusNums(itemSize);
	auto pItemSurplusNums = itemSurplusNums.data();
	CreateAddItems(pItemProtos, itemProps, itemSize,
		flowType, flowParams, isSendPopMsg, &pItemSurplusNums, pNewItemPtr);
	auto itr = std::find_if(itemSurplusNums.begin(), itemSurplusNums.end(),
		[](uint32 num) { return num != 0; });
	if (itr == itemSurplusNums.end()) {
		return;
	}

	auto mailProp = NewSystemMailInstance(m_pOwner->GetGuidLow(),
		(u32)MailFlag::SubjectArgs | (u32)MailFlag::BodyArgs,
		std::to_string(100), std::to_string(101));
	for (size_t i = 0, n = itemSurplusNums.size(); i < n; ++i) {
		if (itemSurplusNums[i] != 0) {
			inst_item_prop itemProp(itemProps[i]);
			itemProp.itemCount = itemSurplusNums[i];
			AppendItem2MailInstance(mailProp, itemProp.Save());
		}
	}
	sMapServer.SendSingleMail(m_pOwner->GetGsId(), mailProp);
}

GErrorCode ItemStorage::CreateAddItem(
	const ItemPrototype* pItemProto, const inst_item_prop& itemProp,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, bool isSendPopMsg,
	uint32* pItemSurplusNum, Item** pNewItemPtr)
{
	return CreateAddItems(pItemProto != NULL ? &pItemProto : NULL,
		&itemProp, 1, flowType, flowParams, isSendPopMsg,
		pItemSurplusNum != NULL ? &pItemSurplusNum : NULL, pNewItemPtr);
}

uint32 ItemStorage::GetFillItemLackSlotCount(
	const ItemPrototype* pItemProto, const inst_item_prop& itemProp,
	bool canMergeable) const
{
	return GetFillItemsLackSlotCount(
		pItemProto != NULL ? &pItemProto : NULL, &itemProp, 1, canMergeable);
}

GErrorCode ItemStorage::CreateAddItems(
	const ItemPrototype* const* pItemProtos,
	const inst_item_prop itemProps[], size_t itemSize,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, bool isSendPopMsg,
	uint32** pItemSurplusNums, Item** pNewItemPtr)
{
	Item* pNewItem = NULL;
	std::vector<uint32> itemNums(itemSize);
	std::vector<const ItemPrototype*> itemProtos(itemSize);
	if (pItemProtos != NULL) {
		std::copy(pItemProtos, pItemProtos + itemSize, itemProtos.begin());
	}
	for (size_t i = 0; i < itemSize; ++i) {
		if (itemProtos[i] == NULL) {
			if (itemProps[i].itemTypeID != 0) {
				itemProtos[i] =
					GetDBEntry<ItemPrototype>(itemProps[i].itemTypeID);
			}
		}
	}
	for (size_t i = 0; i < itemSize; ++i) {
		if (FilterItem(itemProtos[i], itemProps[i])) {
			itemNums[i] = itemProps[i].itemCount;
		}
	}

	for (size_t i = 0; i < itemSize; ++i) {
		auto surplus = SubLeastZero(itemProps[i].itemCount, itemNums[i]);
		for (u32 iSlot = 0; iSlot < m_bagCapacity && surplus > 0; ++iSlot) {
			auto pItem = m_bagItems[iSlot];
			if (pItem == NULL) {
				continue;
			}
			if (!CanMergeItem(pItem, itemProps[i])) {
				continue;
			}
			auto availCnt = std::min(surplus, SubLeastZero(
				itemProtos[i]->itemStack, pItem->GetItemCount()));
			if (availCnt == 0) {
				continue;
			}
			pNewItem = pItem;
			pItem->AddItemCount(availCnt, flowType, flowParams);
			itemNums[i] += availCnt;
			surplus -= availCnt;
		}
	}

	bool isOverflow = false;
	for (size_t i = 0; i < itemSize && !isOverflow; ++i) {
		auto surplus = SubLeastZero(itemProps[i].itemCount, itemNums[i]);
		while (surplus > 0 && !isOverflow) {
			auto iSlot = GetFreeSlot(m_bagItems, m_bagCapacity);
			if (iSlot != ItemSlotInvalid) {
				pNewItem = NewItem(itemProtos[i], itemProps[i], surplus);
				AddNewItem2FreeSlot(
					ItemSlotBagType, iSlot, pNewItem, flowType, flowParams);
				itemNums[i] += pNewItem->GetItemCount();
				surplus -= pNewItem->GetItemCount();
			} else {
				isOverflow = true;
			}
		}
	}

	if (pItemSurplusNums != NULL) {
		std::fill_n(*pItemSurplusNums, itemSize, 0);
		for (size_t i = 0; i < itemSize; ++i) {
			(*pItemSurplusNums)[i] =
				SubLeastZero(itemProps[i].itemCount, itemNums[i]);
		}
	}
	if (pNewItemPtr != NULL) {
		*pNewItemPtr = pNewItem;
	}

	if (isSendPopMsg && itemSize != 0) {
		NetPacket pack(SMSG_GET_ITEM_POPMSG);
		pack << (uint16)itemSize;
		for (size_t i = 0; i < itemSize; ++i) {
			pack << itemProps[i].itemTypeID << itemProps[i].itemCount;
		}
		m_pOwner->SendPacket(pack);
	}

	if (isOverflow) {
		return ErrItemStorageSlotNotEnough;
	} else {
		return CommonSuccess;
	}
}

uint32 ItemStorage::GetFillItemsLackSlotCount(
	const ItemPrototype* const* pItemProtos, const inst_item_prop itemProps[],
	size_t itemSize, bool canMergeable) const
{
	std::vector<uint32> itemNums(itemSize);
	std::vector<const ItemPrototype*> itemProtos(itemSize);
	auto CalcNeedFreeSlotCount = [=, &itemNums, &itemProtos]() {
		uint32 slotCnt = 0;
		for (size_t i = 0; i < itemSize; ++i) {
			auto surplus = SubLeastZero(itemProps[i].itemCount, itemNums[i]);
			if (surplus > 0) {
				slotCnt += DivWithCeil(surplus, itemProtos[i]->itemStack);
			}
		}
		return slotCnt;
	};

	if (pItemProtos != NULL) {
		std::copy(pItemProtos, pItemProtos + itemSize, itemProtos.begin());
	}
	for (size_t i = 0; i < itemSize; ++i) {
		if (itemProtos[i] == NULL) {
			if (itemProps[i].itemTypeID != 0) {
				itemProtos[i] =
					GetDBEntry<ItemPrototype>(itemProps[i].itemTypeID);
			}
		}
	}
	for (size_t i = 0; i < itemSize; ++i) {
		if (CanFilterItem(itemProtos[i])) {
			itemNums[i] = itemProps[i].itemCount;
		}
	}
	auto freeSlotCnt = GetFreeSlotCount(m_bagItems, m_bagCapacity);
	auto lackSlotCnt = SubLeastZero(CalcNeedFreeSlotCount(), freeSlotCnt);
	if (lackSlotCnt == 0 || !canMergeable) {
		return lackSlotCnt;
	}

	uint32 extItemNums[ItemBagSlotCount]{};
	for (size_t i = 0; i < itemSize; ++i) {
		auto surplus = SubLeastZero(itemProps[i].itemCount, itemNums[i]);
		for (u32 iSlot = 0; iSlot < m_bagCapacity && surplus > 0; ++iSlot) {
			auto pItem = m_bagItems[iSlot];
			if (pItem == NULL) {
				continue;
			}
			auto& extItemNum = extItemNums[iSlot];
			if (!CanMergeItem(pItem, itemProps[i], extItemNum)) {
				continue;
			}
			auto availCnt = std::min(surplus, SubLeastZero(
				itemProtos[i]->itemStack, pItem->GetItemCount() + extItemNum));
			if (availCnt == 0) {
				continue;
			}
			extItemNum += availCnt;
			itemNums[i] += availCnt;
			surplus -= availCnt;
		}
	}

	return SubLeastZero(CalcNeedFreeSlotCount(), freeSlotCnt);
}

GErrorCode ItemStorage::ArrangeItems(ItemSlotType type)
{
	bool isOK = false;
	switch (type) {
	case ItemSlotBagType:
	case ItemSlotBankType:
		isOK = true;
		break;
	}
	if (!isOK) {
		return InvalidRequest;
	}

	Item** pItemCluster = NULL;
	uint32 slotCapacity = 0;
	std::tie(pItemCluster, slotCapacity) = GetItemClusterMutable(type);

	std::vector<Item*> allItems, dltItems;
	allItems.reserve(slotCapacity), dltItems.reserve(slotCapacity);
	for (u32 iSlot = 0; iSlot < slotCapacity; ++iSlot) {
		auto pCurItem = pItemCluster[iSlot];
		if (pCurItem == NULL) {
			continue;
		}
		auto itr = std::find_if(allItems.begin(), allItems.end(),
			[=](Item* pItem) { return CanMergeItem(pItem, pCurItem); });
		if (itr == allItems.end()) {
			allItems.push_back(pCurItem);
			continue;
		}
		auto pItem = *itr;
		auto availCnt = std::min(pCurItem->GetItemCount(), SubLeastZero(
			pItem->GetItemProto()->itemStack, pItem->GetItemCount()));
		pItem->AddItemCount(availCnt, IFT_ITEM_ARRANGE, {}, ICF_NO_EFFECT_FLAGS);
		if (pCurItem->GetItemCount() <= availCnt) {
			dltItems.push_back(pCurItem);
			continue;
		}
		pCurItem->SubItemCount(availCnt, IFT_ITEM_ARRANGE, {}, ICF_NO_EFFECT_FLAGS);
		allItems.push_back(pCurItem);
	}

	std::sort(allItems.begin(), allItems.end(), [](Item* pItem1, Item* pItem2) {
		return pItem1->GetItemTypeID() < pItem2->GetItemTypeID();
	});

	std::fill_n(pItemCluster, slotCapacity, nullptr);
	for (size_t i = 0, n = allItems.size(); i < n; ++i) {
		pItemCluster[i] = allItems[i];
		pItemCluster[i]->SetItemSlot(type, (u32)i);
	}
	for (auto pItem : dltItems) {
		delete pItem;
	}

	NetPacket pack(SMSG_ARRANGE_ITEMS);
	pack << type;
	PackItemCluster(pack, pItemCluster, (u32)allItems.size());
	m_pOwner->SendPacket(pack);

	return CommonSuccess;
}

GErrorCode ItemStorage::SwapItem(
	ItemSlotType type, uint32 slot, ItemSlotType tgtType, uint32 tgtSlot)
{
	bool isOK = false;
	switch (type) {
	case ItemSlotBagType:
	case ItemSlotBankType:
		switch (tgtType) {
		case ItemSlotBagType:
		case ItemSlotBankType:
			isOK = true;
			break;
		}
		break;
	}
	if (!isOK) {
		return InvalidRequest;
	}

	auto pItemCluster = GetItemClusterMutable(type, slot).first;
	if (pItemCluster == NULL || pItemCluster[slot] == NULL) {
		return InvalidRequest;
	}

	Item** pTgtItemCluster = NULL;
	if (tgtSlot != ItemSlotInvalid) {
		pTgtItemCluster = GetItemClusterMutable(tgtType, tgtSlot).first;
		if (pTgtItemCluster == NULL) {
			return InvalidRequest;
		}
	} else if (type != tgtType) {
		auto rst = GetItemClusterMutable(tgtType);
		tgtSlot = GetFreeSlot(pTgtItemCluster = rst.first, rst.second);
		if (tgtSlot == ItemSlotInvalid) {
			return ErrItemStorageSlotNotEnough;
		}
	} else {
		return InvalidRequest;
	}

	SwapItem(pItemCluster, type, slot, pTgtItemCluster, tgtType, tgtSlot,
		IFT_ITEM_SWAP, {});

	return CommonSuccess;
}

GErrorCode ItemStorage::SplitItem(ItemSlotType type, uint32 slot, uint32 num)
{
	bool isOK = false;
	switch (type) {
	case ItemSlotBagType:
	case ItemSlotBankType:
		isOK = true;
		break;
	}
	if (!isOK) {
		return InvalidRequest;
	}

	auto rst = GetItemClusterMutable(type, slot);
	if (rst.first == NULL || rst.first[slot] == NULL) {
		return InvalidRequest;
	}

	auto tgtSlot = GetFreeSlot(rst.first, rst.second);
	if (tgtSlot == ItemSlotInvalid) {
		return ErrItemStorageSlotNotEnough;
	}

	auto pItem = rst.first[slot];
	if (pItem->GetItemCount() <= num || num == 0) {
		return InvalidRequest;
	}

	pItem->SubItemCount(num, IFT_ITEM_SPLIT, {}, ICF_PREVENT_SYNC_VALUE);

	auto pNewItem = NewItem(pItem->GetItemProto(), pItem->GetItemProp(), num);
	AddNewItem2FreeSlot(
		type, tgtSlot, pNewItem, IFT_ITEM_SPLIT, {}, ICF_PREVENT_SYNC_VALUE);

	return CommonSuccess;
}

GErrorCode ItemStorage::EquipItem(uint32 slot, uint32 tgtSlot)
{
	if (slot >= m_bagCapacity || m_bagItems[slot] == NULL) {
		return InvalidRequest;
	}

	auto pEquipItem = m_bagItems[slot];
	auto&& [equipSlotStart, equipSlotEnd] =
		ItemProto2EquipSlot(pEquipItem->GetItemProto());
	if (equipSlotStart == ItemSlotInvalid) {
		return InvalidRequest;
	}

	if (tgtSlot != ItemSlotInvalid) {
		if (tgtSlot < equipSlotStart || tgtSlot >= equipSlotEnd) {
			return InvalidRequest;
		}
	} else {
		for (tgtSlot = equipSlotStart; tgtSlot < equipSlotEnd; ++tgtSlot) {
			if (m_equipItems[tgtSlot] == NULL) {
				break;
			}
		}
		if (tgtSlot >= equipSlotEnd) {
			return ErrItemEquipSlotNotEmpty;
		}
	}

	SwapItem(m_equipItems, ItemSlotEquipType, tgtSlot, m_bagItems, ItemSlotBagType, slot,
		IFT_ITEM_EQUIP, {});

	return CommonSuccess;
}

GErrorCode ItemStorage::UnequipItem(uint32 slot, uint32 tgtSlot)
{
	if (slot >= ItemEquipSlotCount || m_equipItems[slot] == NULL) {
		return InvalidRequest;
	}

	if (tgtSlot != ItemSlotInvalid) {
		if (tgtSlot > m_bagCapacity) {
			return InvalidRequest;
		}
		if (m_bagItems[tgtSlot] != NULL) {
			return ErrItemSlotNotEmpty;
		}
	} else {
		auto tgtItemSlot = GetFreeSlot(m_bagItems, m_bagCapacity);
		if (tgtItemSlot == ItemSlotInvalid) {
			return ErrItemStorageSlotNotEnough;
		}
	}

	SwapItem(m_equipItems, ItemSlotEquipType, slot, m_bagItems, ItemSlotBagType, tgtSlot,
		IFT_ITEM_UNEQUIP, {});

	return CommonSuccess;
}

void ItemStorage::AddNewItem2FreeSlot(ItemSlotType type, uint32 slot,
	Item* pItem, ITEM_FLOW_TYPE flowType, params<uint32> flowParams,
	uint32 flags)
{
	Item** pItems = GetItemClusterMutable(type, slot).first;
	if (pItems == NULL) {
		WLOG("AddNewItem2FreeSlot failed, param error %d[%u].", type, slot);
		delete pItem;
		return;
	}

	Item*& pItemRef = pItems[slot];
	if (pItemRef != NULL) {
		WLOG("AddNewItem2FreeSlot failed, slot isn't free %d[%u].", type, slot);
		delete pItem;
		return;
	}

	pItem->SetItemSlot(type, slot);
	pItemRef = pItem;

	if ((flags & ICF_PREVENT_SYNC_VALUE) == 0) {
		auto pQuestStorage = m_pOwner->GetQuestStorage();
		pQuestStorage->SetItemCountDirty(pItem->GetItemTypeID());
		pQuestStorage->OnHaveItem(
			pItem->GetItemTypeID(), pItem->GetItemCount(), flowType, flowParams);
	}

	NetPacket pack(SMSG_CREATE_ITEM);
	pack << type << slot;
	pItem->GetItemProp().Save(pack);
	m_pOwner->SendPacket(pack);
}

Item* ItemStorage::NewItem(const ItemPrototype* pItemProto,
	const inst_item_prop& itemProp, uint32 itemCount, bool isNew) const
{
	Item* pItem = new Item(m_pOwner);
	GErrorCode err = pItem->Load(pItemProto, itemProp, itemCount, isNew);
	if (err != CommonSuccess) {
		WLOG("NewItem failed, %u <-> %u, error %d.",
			pItemProto->itemTypeID, itemProp.itemTypeID, err);
		delete pItem;
		return NULL;
	}
	return pItem;
}

void ItemStorage::SwapItem(Item** pItems1, ItemSlotType type1, uint32 slot1,
	Item** pItems2, ItemSlotType type2, uint32 slot2,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams)
{
	if (type1 == type2 && slot1 == slot2) {
		return;
	}

	std::swap(pItems1[slot1], pItems2[slot2]);
	if (pItems1[slot1] != NULL) {
		pItems1[slot1]->SetItemSlot(type1, slot1);
	}
	if (pItems2[slot2] != NULL) {
		pItems2[slot2]->SetItemSlot(type2, slot2);
	}

	if (type1 != type2) {
		Item *pToBagItem = NULL, *pFromBagItem = NULL;
		if (type1 == ItemSlotBagType) {
			pToBagItem = pItems1[slot1], pFromBagItem = pItems2[slot2];
		} else if (type2 == ItemSlotBagType) {
			pToBagItem = pItems2[slot2], pFromBagItem = pItems1[slot1];
		}
		auto pQuestStorage = m_pOwner->GetQuestStorage();
		if (pToBagItem != NULL) {
			pQuestStorage->SetItemCountDirty(pToBagItem->GetItemTypeID());
			pQuestStorage->OnHaveItem(pToBagItem->GetItemTypeID(),
				pToBagItem->GetItemCount(), flowType, flowParams);
		}
		if (pFromBagItem != NULL) {
			pQuestStorage->SetItemCountDirty(pFromBagItem->GetItemTypeID());
			pQuestStorage->OnHaveItem(pFromBagItem->GetItemTypeID(),
				0, flowType, flowParams);
		}
	}

	NetPacket pack(SMSG_SWAP_ITEM);
	pack << type1 << slot1 << type2 << slot2;
	m_pOwner->SendPacket(pack);
}

bool ItemStorage::CanFilterItem(const ItemPrototype* pItemProto) const
{
	if (pItemProto == NULL) {
		return true;
	}
	switch ((ItemClass)pItemProto->itemClass) {
	case ItemClass::Score:
		return true;
	}
	return false;
}

bool ItemStorage::FilterItem(const ItemPrototype* pItemProto,
	const inst_item_prop& itemProp)
{
	if (pItemProto == NULL) {
		return true;
	}
	switch ((ItemClass)pItemProto->itemClass) {
	case ItemClass::Score:
		m_pOwner->SendOperating4GainScore(
			pItemProto->itemParams[1], pItemProto->itemParams[0]);
		return true;
	}
	return false;
}

bool ItemStorage::CanMergeItem(Item* pItem, Item* pOtherItem)
{
	return CanMergeItem(pItem, pOtherItem->GetItemProp());
}

bool ItemStorage::CanMergeItem(
	Item* pItem, const inst_item_prop& itemProp, uint32 extItemNum)
{
	const ItemPrototype* pItemProto = pItem->GetItemProto();
	const inst_item_prop& tgtItemProp = pItem->GetItemProp();
	if (tgtItemProp.itemTypeID != itemProp.itemTypeID) {
		return false;
	}
	if (tgtItemProp.itemOwner != itemProp.itemOwner) {
		return false;
	}
	if (tgtItemProp.itemCount + extItemNum >= pItemProto->itemStack) {
		return false;
	}
	return true;
}

uint32 ItemStorage::GetFreeSlotCount(Item* const pItems[], uint32 slotCapacity)
{
	return (uint32)std::count(pItems, pItems + slotCapacity, nullptr);
}

uint32 ItemStorage::GetFreeSlot(Item* const pItems[], uint32 slotCapacity)
{
	auto itr = std::find(pItems, pItems + slotCapacity, nullptr);
	return itr != pItems + slotCapacity ? u32(itr - pItems) : ItemSlotInvalid;
}

void ItemStorage::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	pck << m_bagCapacity << m_bankCapacity;
	PackItemCluster(pck, m_equipItems, ItemEquipSlotCount);
	PackItemCluster(pck, m_bagItems, m_bagCapacity);
	PackItemCluster(pck, m_bankItems, m_bankCapacity);
}

void ItemStorage::PackItemCluster(
	INetPacket& pck, Item* const pItems[], uint32 slotCapacity)
{
	size_t anchor = pck.Placeholder<uint16>(0);
	uint16 itemNum = 0;
	for (u32 iSlot = 0; iSlot < slotCapacity; ++iSlot) {
		auto pItem = pItems[iSlot];
		if (pItem != NULL) {
			pck << iSlot;
			pItem->GetItemProp().Save(pck);
			itemNum += 1;
		}
	}
	if (itemNum != 0) {
		pck.Put(anchor, itemNum);
	}
}

std::string ItemStorage::SaveEffectItems() const
{
	TextPacker packer;
	SaveItemCluster(m_equipItems, ItemEquipSlotCount, packer);
	return packer.str();
}

std::string ItemStorage::SaveAllOtherItems() const
{
	TextPacker packer;
	SaveItemCluster(m_bagItems, m_bagCapacity, packer);
	SaveItemCluster(m_bankItems, m_bankCapacity, packer);
	return packer.str();
}

void ItemStorage::LoadEffectItems(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	LoadItemCluster(
		ItemSlotEquipType, m_equipItems, ItemEquipSlotCount, unpacker);
}

void ItemStorage::LoadAllOtherItems(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
	LoadItemCluster(ItemSlotBagType, m_bagItems, m_bagCapacity, unpacker);
	LoadItemCluster(ItemSlotBankType, m_bankItems, m_bankCapacity, unpacker);
}

void ItemStorage::SaveItemCluster(
	Item* const pItems[], uint32 slotCapacity, TextPacker& packer) const
{
	for (u32 iSlot = 0; iSlot < slotCapacity; ++iSlot) {
		auto pItem = pItems[iSlot];
		if (pItem != NULL) {
			packer << iSlot;
			pItem->GetItemProp().Save(packer);
			packer.PutDelimiter(';');
		}
	}
	packer.PutAnchor(';');
}

void ItemStorage::LoadItemCluster(ItemSlotType slotType,
	Item* pItems[], uint32 slotCapacity, TextUnpacker& unpacker) const
{
	while (!unpacker.IsEmpty() && !unpacker.IsAnchor(';')) {
		uint32 iSlot;
		inst_item_prop itemProp;
		unpacker >> iSlot;
		itemProp.Load(unpacker);
		if (iSlot >= slotCapacity || itemProp.itemCount == 0) {
			continue;
		}
		auto pItemProto = GetDBEntry<ItemPrototype>(itemProp.itemTypeID);
		if (pItemProto == NULL) {
			continue;
		}
		auto pItem = NewItem(pItemProto, itemProp, 0, false);
		if (pItem == NULL) {
			continue;
		}
		pItem->SetItemSlot(slotType, iSlot);
		pItems[iSlot] = pItem;
	}
}

std::string ItemStorage::SaveStorageStatus() const
{
	TextPacker packer;
	return packer.str();
}

void ItemStorage::LoadStorageStatus(const std::string& data)
{
	TextUnpacker unpacker(data.c_str());
}
